package cn.com.utils.crypto;

import cn.com.consts.MsgCd;
import cn.com.utils.WebUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.File;
import java.io.FileNotFoundException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.List;
import java.util.Optional;

/**
 * 数据加密工具 数据加密
 */
@Slf4j
public class DataCryptoUtil {

    private static final int ITERATION_COUNT = 27500;

    private static final String HASH_ALGORITHM = "PBKDF2WithHmacSHA256";

    private static final String REGEX_NUM = "[1-9]\\d*";

    private static final int HASH_BIT_SIZE = 256;


    /**
     * base文件路径
     */
    private static final String VAR_RC_BASE_CONSTANT = "VAR_RC_BASE";

    /**
     * stack文件路径
     */
    private static final String VAR_RC_STACK_CONSTANT = "VAR_RC_STACK";

    private static final String BASE_PATH = "cloudstar.base.path";

    private static final String STACK_PATH = "cloudstar.stack.path";

    private static final String SPLIT_SIGN = "cryto.decrypt.split.sign";

    private static final String ERROR_PREFIX = "cryto.decrypt.error";

    private static final String KEY_PATH = "cloudstar.key.path";


    /**
     * 分隔符
     */
    private static final String SPLIT_SIGN_STRING;

    private static final String ERROR_PREFIX_STRING;

    static {
        // 初始化工作密钥
        String basePath = System.getProperty(BASE_PATH, System.getenv(VAR_RC_BASE_CONSTANT));
        String stackPath = System.getProperty(STACK_PATH, System.getenv(VAR_RC_STACK_CONSTANT));
        SPLIT_SIGN_STRING = SettingUtils.get(SPLIT_SIGN);
        ERROR_PREFIX_STRING = SettingUtils.get(ERROR_PREFIX);
        if (Clibrary.INSTANCE != null) {
            if (ObjectUtil.isEmpty(basePath) && ObjectUtil.isEmpty(stackPath)) {
                //获取本地base，stack
                basePath = getCanonicalPath("base");
                stackPath = getCanonicalPath("stack");
            }
            Clibrary.INSTANCE.Init(new GoString.ByValue(basePath), new GoString.ByValue(stackPath));
        }
    }

    /**
     * 加密方法
     *
     * @param data 数据
     * @return {@link String}
     */
    static String encrypt(String data) {
        if (StrUtil.isEmpty(data)) {
            return data;
        }
        String encryptResult = Clibrary.INSTANCE.Encrypt(new GoString.ByValue(data));
        if (StrUtil.isEmpty(encryptResult) || StrUtil.startWith(encryptResult, ERROR_PREFIX_STRING, true)) {
            log.error("加密文本处理失败,{}", encryptResult);
            throw new IllegalArgumentException(WebUtil.getMessage(MsgCd.ERR_MSG_BSS_12));
        }
        return encryptResult;
    }

    /**
     * 解密方法
     *
     * @param content 数据
     * @return {@link String}
     */
    static String decrypt(String content) {
        if (StrUtil.isEmpty(content)) {
            return content;
        }
        String decryptResult = "";
        try {
            List<String> contentList = StrUtil.isBlank(SPLIT_SIGN_STRING) ?
                    CollectionUtil.newArrayList(content) :
                    StrUtil.split(content, SPLIT_SIGN_STRING);
            if (CollectionUtil.isEmpty(contentList)) {
                return content;
            }
            if (contentList.size() == 1) {
                decryptResult = Clibrary.INSTANCE.Decrypt(new GoString.ByValue(contentList.get(0)));
            } else if (contentList.size() == 2) {
                if (contentList.get(0).matches(REGEX_NUM)) {
                    decryptResult = Clibrary.INSTANCE.DecryptWithIndex(Integer.parseInt(contentList.get(0)),
                            new GoString.ByValue(contentList.get(1)));
                } else {
                    // 打异常日志  直接返回原串
                    log.error("解密失败，不匹配解密规则");
                    return content;
                }
            } else {
                log.warn("解密文本按指定字符分隔后长度不对，{}", contentList.size());
                return content;
            }
            if (StrUtil.isEmpty(decryptResult) || StrUtil.startWith(decryptResult, ERROR_PREFIX_STRING)) {
                // 打异常日志  直接返回原串
                return content;
            }
        } catch (NullPointerException p) {
            log.error("出现空指针异常", p.getMessage());
            return content;
        } catch (NumberFormatException n) {
            log.error("解密字符转换异常", n.getMessage());
            return content;

        } catch (Exception e) {
            log.error("解密处理失败:{}", e.getMessage());
            return content;
        }
        return decryptResult;
    }


    /**
     * 获取密文
     *
     * @param password 密码明文
     * @param salt     加盐
     * @return {@link String}
     */
    static String encodeHash(String password, byte[] salt) {
        return encodeHash(password, salt, ITERATION_COUNT);
    }

    static String encodeHash(String password, byte[] salt, int iteration) {
        // Returns only the last part of whole encoded password
        SecretKeyFactory keyFactory = null;
        try {
            keyFactory = SecretKeyFactory.getInstance(HASH_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            log.error("Could NOT retrieve {} algorithm", HASH_ALGORITHM, e);
            return null;
        }
        KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iteration, HASH_BIT_SIZE);

        SecretKey secret = null;
        try {
            secret = keyFactory.generateSecret(keySpec);
        } catch (InvalidKeySpecException e) {
            log.error("Could NOT generate secret key", e);
            return null;
        }

        // 使用Base64进行转码密文
        return Base64.getEncoder().encodeToString(secret.getEncoded());
    }

    /**
     * 最终返回的整串密文
     *
     * @param password 密码明文
     * @return {@link Credential}
     */
    @SneakyThrows
    static Credential encode(String password) {
        byte[] salt = new byte[16];
        SecureRandom.getInstanceStrong().nextBytes(salt);

        String hash = encodeHash(password, salt);
        return new Credential(hash, Base64.getEncoder().encodeToString(salt), ITERATION_COUNT, HASH_ALGORITHM);
    }


    /**
     * 获取指定文件
     *
     * @param fileName 文件名
     * @return {@link File}
     */
    @SneakyThrows
    static File getKeyFile(String fileName) {
        final String keyPath = SettingBurstUtils.get(KEY_PATH);
        final File file = FileUtil.file(keyPath);
        if (file.isDirectory()) {
            final File[] files = file.listFiles();
            if (files == null) {
                throw new FileNotFoundException(WebUtil.getMessage(MsgCd.ERR_MSG_BSS_1638171384));
            }
            for (File f : files) {
                if (f.getName().equalsIgnoreCase(fileName)) {
                    return f;
                }
            }
        }
        return null;
    }


    /**
     * 获取文件绝对路径，标准化获取
     */
    @SneakyThrows
    static String getCanonicalPath(String fileName) {
        return Optional.ofNullable(getKeyFile(fileName)).map(FileUtil::getCanonicalPath).orElse(null);
    }

}
